"""
Git操作服务
"""

import os
import shutil
import tempfile
from pathlib import Path
from typing import Optional, Dict, Any, List
from urllib.parse import urlparse

import git
from git import porcelain
from dulwich.repo import Repo

from models.repository import Repository
from config import settings
from utils.logger import get_logger

logger = get_logger(__name__)


class GitOperationError(Exception):
    """Git操作异常"""
    pass


class GitAuthenticationError(GitOperationError):
    """Git认证异常"""
    pass


class GitService:
    """Git操作服务"""

    def __init__(self):
        self.repo_cache_dir = Path(settings.repo_cache_dir)
        self.repo_cache_dir.mkdir(parents=True, exist_ok=True)

    def _get_cache_dir(self, repo_id: int) -> Path:
        """获取仓库缓存目录"""
        return self.repo_cache_dir / f"repo_{repo_id}"

    def _prepare_auth(self, repository: Repository) -> Dict[str, Any]:
        """准备认证信息"""
        auth_config = {}

        if not repository.auth_info or not repository.auth_type:
            return auth_config

        if repository.auth_type == "token":
            # Token认证
            token = repository.auth_info.get("token")
            if token:
                # 解析URL并插入token
                parsed_url = urlparse(repository.url)
                if parsed_url.scheme in ["http", "https"]:
                    auth_url = f"{parsed_url.scheme}://token:{token}@{parsed_url.netloc}{parsed_url.path}"
                    auth_config["url"] = auth_url

        elif repository.auth_type == "username_password":
            # 用户名密码认证
            username = repository.auth_info.get("username")
            password = repository.auth_info.get("password")
            if username and password:
                parsed_url = urlparse(repository.url)
                if parsed_url.scheme in ["http", "https"]:
                    auth_url = f"{parsed_url.scheme}://{username}:{password}@{parsed_url.netloc}{parsed_url.path}"
                    auth_config["url"] = auth_url

        elif repository.auth_type == "ssh_key":
            # SSH密钥认证
            ssh_key = repository.auth_info.get("private_key")
            if ssh_key:
                # 创建临时SSH密钥文件
                ssh_key_file = self.repo_cache_dir / f"ssh_key_{repository.id}"
                with open(ssh_key_file, "w") as f:
                    f.write(ssh_key)
                os.chmod(ssh_key_file, 0o600)

                auth_config["ssh_command"] = f"ssh -i {ssh_key_file} -o StrictHostKeyChecking=no"

        return auth_config

    async def test_connection(self, repository: Repository) -> Dict[str, Any]:
        """测试仓库连接"""
        try:
            cache_dir = self._get_cache_dir(repository.id)

            # 清理之前的缓存
            if cache_dir.exists():
                shutil.rmtree(cache_dir)

            auth_config = self._prepare_auth(repository)
            repo_url = auth_config.get("url", repository.url)

            # 尝试克隆仓库（浅克隆）
            repo = git.Repo.clone_from(
                repo_url,
                cache_dir,
                depth=1,
                single_branch=True
            )

            # 获取分支信息
            branches = [branch.name for branch in repo.branches]
            latest_commit = repo.head.commit.hexsha

            result = {
                "status": "connected",
                "branches": branches,
                "last_commit": latest_commit,
                "tested_at": "2024-01-01T00:00:00Z"  # TODO: 使用实际时间
            }

            logger.info("仓库连接测试成功", repo_id=repository.id, branches_count=len(branches))
            return result

        except git.exc.GitCommandError as e:
            error_msg = str(e)
            if "authentication failed" in error_msg.lower():
                raise GitAuthenticationError(f"认证失败: {error_msg}")
            else:
                raise GitOperationError(f"连接失败: {error_msg}")

        except Exception as e:
            logger.error("仓库连接测试失败", repo_id=repository.id, error=str(e))
            raise GitOperationError(f"连接测试失败: {str(e)}")

    async def clone_repository(self, repository: Repository, target_dir: Optional[Path] = None) -> Path:
        """克隆仓库"""
        try:
            if target_dir is None:
                target_dir = self._get_cache_dir(repository.id)

            # 清理目标目录
            if target_dir.exists():
                shutil.rmtree(target_dir)

            auth_config = self._prepare_auth(repository)
            repo_url = auth_config.get("url", repository.url)

            # 设置环境变量（SSH密钥）
            env = os.environ.copy()
            if "ssh_command" in auth_config:
                env["GIT_SSH_COMMAND"] = auth_config["ssh_command"]

            # 克隆仓库
            repo = git.Repo.clone_from(repo_url, target_dir, env=env)

            logger.info("仓库克隆成功", repo_id=repository.id, target_dir=str(target_dir))
            return target_dir

        except git.exc.GitCommandError as e:
            logger.error("仓库克隆失败", repo_id=repository.id, error=str(e))
            raise GitOperationError(f"克隆失败: {str(e)}")

    async def pull_changes(self, repo_dir: Path, branch: str = "main") -> str:
        """拉取最新代码"""
        try:
            if not repo_dir.exists():
                raise GitOperationError("仓库目录不存在")

            repo = git.Repo(repo_dir)

            # 切换到指定分支
            if branch in repo.heads:
                repo.heads[branch].checkout()
            else:
                # 尝试创建并跟踪远程分支
                origin = repo.remotes.origin
                origin.fetch()
                repo.create_head(branch, origin.refs[branch]).set_tracking_branch(origin.refs[branch])
                repo.heads[branch].checkout()

            # 拉取最新代码
            origin = repo.remotes.origin
            origin.pull(branch)

            # 获取最新提交ID
            latest_commit = repo.head.commit.hexsha

            logger.info("代码拉取成功", repo_dir=str(repo_dir), branch=branch, commit=latest_commit)
            return latest_commit

        except git.exc.GitCommandError as e:
            logger.error("代码拉取失败", repo_dir=str(repo_dir), branch=branch, error=str(e))
            raise GitOperationError(f"拉取失败: {str(e)}")

    async def push_to_target(
        self,
        repo_dir: Path,
        target_repository: Repository,
        branch: str = "main",
        force_push: bool = False
    ) -> str:
        """推送到目标仓库"""
        try:
            if not repo_dir.exists():
                raise GitOperationError("仓库目录不存在")

            repo = git.Repo(repo_dir)

            # 准备认证信息
            auth_config = self._prepare_auth(target_repository)

            # 添加远程仓库（如果不存在）
            remote_name = f"target_{target_repository.id}"
            if remote_name not in repo.remotes:
                target_url = auth_config.get("url", target_repository.url)
                repo.create_remote(remote_name, target_url)

            remote = repo.remotes[remote_name]

            # 设置环境变量（SSH密钥）
            env = os.environ.copy()
            if "ssh_command" in auth_config:
                env["GIT_SSH_COMMAND"] = auth_config["ssh_command"]

            # 推送代码
            push_args = [f"{branch}:{branch}"]
            if force_push:
                push_args.append("--force")

            result = remote.push(push_args, env=env)

            if not result:
                raise GitOperationError("推送失败，没有返回结果")

            push_result = result[0]
            if push_result.flags & push_result.ERROR:
                raise GitOperationError(f"推送失败: {push_result.summary}")

            # 获取推送后的提交ID
            latest_commit = repo.head.commit.hexsha

            logger.info(
                "代码推送成功",
                source_repo=str(repo_dir),
                target_repo_id=target_repository.id,
                branch=branch,
                commit=latest_commit,
                force_push=force_push
            )

            return latest_commit

        except git.exc.GitCommandError as e:
            logger.error(
                "代码推送失败",
                source_repo=str(repo_dir),
                target_repo_id=target_repository.id,
                branch=branch,
                error=str(e)
            )
            raise GitOperationError(f"推送失败: {str(e)}")

    async def get_commit_id(self, repo_dir: Path, branch: str = "main") -> str:
        """获取指定分支的最新提交ID"""
        try:
            if not repo_dir.exists():
                raise GitOperationError("仓库目录不存在")

            repo = git.Repo(repo_dir)

            # 切换到指定分支
            if branch in repo.heads:
                repo.heads[branch].checkout()
            else:
                raise GitOperationError(f"分支 {branch} 不存在")

            # 获取最新提交ID
            latest_commit = repo.head.commit.hexsha

            logger.debug("获取提交ID成功", repo_dir=str(repo_dir), branch=branch, commit=latest_commit)
            return latest_commit

        except git.exc.GitCommandError as e:
            logger.error("获取提交ID失败", repo_dir=str(repo_dir), branch=branch, error=str(e))
            raise GitOperationError(f"获取提交ID失败: {str(e)}")

    async def get_branches(self, repo_dir: Path) -> List[str]:
        """获取仓库所有分支"""
        try:
            if not repo_dir.exists():
                raise GitOperationError("仓库目录不存在")

            repo = git.Repo(repo_dir)

            # 获取本地分支
            local_branches = [branch.name for branch in repo.heads]

            # 获取远程分支
            remote_branches = []
            if repo.remotes:
                origin = repo.remotes.origin
                origin.fetch()
                for ref in origin.refs:
                    if ref.remote_head:
                        remote_branches.append(ref.remote_head)

            all_branches = list(set(local_branches + remote_branches))

            logger.debug("获取分支列表成功", repo_dir=str(repo_dir), branches_count=len(all_branches))
            return all_branches

        except git.exc.GitCommandError as e:
            logger.error("获取分支列表失败", repo_dir=str(repo_dir), error=str(e))
            raise GitOperationError(f"获取分支列表失败: {str(e)}")

    async def add_files(self, repo_dir: Path, files: List[Dict[str, Any]]) -> None:
        """添加文件到仓库"""
        try:
            if not repo_dir.exists():
                raise GitOperationError("仓库目录不存在")

            repo = git.Repo(repo_dir)

            for file_info in files:
                source_path = file_info.get("source_path")
                target_path = file_info.get("target_path")

                if not source_path or not target_path:
                    continue

                source_file = Path(source_path)
                target_file = repo_dir / target_path

                # 确保目标目录存在
                target_file.parent.mkdir(parents=True, exist_ok=True)

                # 复制文件
                shutil.copy2(source_file, target_file)

                # 添加到Git
                repo.git.add(str(target_file))

            logger.info("文件添加成功", repo_dir=str(repo_dir), files_count=len(files))

        except Exception as e:
            logger.error("文件添加失败", repo_dir=str(repo_dir), error=str(e))
            raise GitOperationError(f"添加文件失败: {str(e)}")

    async def commit_changes(self, repo_dir: Path, message: str = "Auto sync files") -> str:
        """提交变更"""
        try:
            if not repo_dir.exists():
                raise GitOperationError("仓库目录不存在")

            repo = git.Repo(repo_dir)

            # 检查是否有变更
            if not repo.is_dirty(untracked_files=True):
                logger.info("没有需要提交的变更", repo_dir=str(repo_dir))
                return repo.head.commit.hexsha

            # 提交变更
            repo.git.add(A=True)
            commit = repo.index.commit(message)

            logger.info("变更提交成功", repo_dir=str(repo_dir), commit=commit.hexsha)
            return commit.hexsha

        except git.exc.GitCommandError as e:
            logger.error("变更提交失败", repo_dir=str(repo_dir), error=str(e))
            raise GitOperationError(f"提交失败: {str(e)}")

    def cleanup_cache(self, repo_id: Optional[int] = None) -> None:
        """清理缓存"""
        try:
            if repo_id is not None:
                # 清理指定仓库的缓存
                cache_dir = self._get_cache_dir(repo_id)
                if cache_dir.exists():
                    shutil.rmtree(cache_dir)
                    logger.info("仓库缓存清理成功", repo_id=repo_id)
            else:
                # 清理所有缓存
                if self.repo_cache_dir.exists():
                    shutil.rmtree(self.repo_cache_dir)
                    self.repo_cache_dir.mkdir(parents=True, exist_ok=True)
                    logger.info("所有仓库缓存清理成功")

        except Exception as e:
            logger.error("缓存清理失败", repo_id=repo_id, error=str(e))


# 全局Git服务实例
git_service = GitService()